#region Microsoft Public License
/*
 * (c) Copyright Chenjun Wu. http://utablesdk.codeplex.com
 * 
 * This source is subject to the Microsoft Public License (Ms-PL).
 * Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
 * All other rights reserved.
 */
#endregion

﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Media;

namespace UTable.Objects.Handlers.Gestures
{
    public class TwoContactsGestureRecognizer: GestureRecognizer
    {
        #region Private Fields

        Point _originalPosition1;

        Point _originalPosition2;

        Point _originalCenter;

        Point _previousPosition1;

        Point _previousPosition2;

        int _contactId1;

        int _contactId2;

        private Range _contactNumberRange;

        private OneContactStateType _contactState1;

        private OneContactStateType _contactState2;

        public double MaxHoldDistance;

        #endregion

        #region Properties

        /// <summary>
        /// The zoom factor is calculated by:
        /// delta = 1 + (currentDistance - previousDistance) / (previousDistance + ZoomParaA) * ZoomParaB;
        /// The default ZoomParaA is 400, the default ZoomParaB is 0.9
        /// </summary>
        public double ZoomParaA { get; set; }

        /// <summary>
        /// The zoom factor is calculated by:
        /// delta = 1 + (currentDistance - previousDistance) / (previousDistance + ZoomParaA) * ZoomParaB;
        /// The default ZoomParaA is 400, the default ZoomParaB is 0.9
        /// </summary>
        public double ZoomParaB { get; set; }

        /// <summary>
        /// The max zoom factor when a zoom gesture is detected. 
        /// If the zoom factor calculated is larger than MaxZoomFactor + 1, or less than 1 - MaxZoomFactor, the factor will be reduced to this value.
        /// The default is 0.04
        /// </summary>
        public double MaxZoomFactor { get; set; }

        #endregion

        #region Constructor

        public TwoContactsGestureRecognizer()
        {
            _contactNumberRange = new Range(2, 2);

            _contactState1 = _contactState2 = OneContactStateType.Hold;

            EnabledGestures = new List<TwoContactsGestureType>();

            MaxHoldDistance = 20;

            MaxZoomFactor = 0.04;

            ZoomParaA = 400;

            ZoomParaB = 0.9;
        }

        #endregion

        #region GestureRecognizer Members

        public List<TwoContactsGestureType> EnabledGestures;

        public Range ContactNumberRange
        {
            get { return _contactNumberRange; }
        }

        public int Priority
        {
            get { return 10; }
        }

        public bool IsSupportedGesture(Type gesture)
        {
            return gesture.IsSubclassOf(typeof(TwoContactsGesture));
        }

        public void StartRecognize(GestureInfo info)
        {
            _contactId1 = info.Contacts[0].ID;
            _contactId2 = info.Contacts[1].ID;
            _originalPosition1 = _previousPosition1 = info.Contacts[0].CurrentPosition;
            _originalPosition2 = _previousPosition2 = info.Contacts[1].CurrentPosition;
            _originalCenter = new Point((_originalPosition1.X + _originalPosition2.X) / 2, (_originalPosition1.Y + _originalPosition2.Y) / 2);

            if (GestureDetected != null)
                GestureDetected(this, new GestureDetectedEventArgs(new TwoContactsStartGesture(info), 1));
        }

        public void Recognizing(GestureInfo info)
        {
            // determine the state of each contact
            if (_contactState2 == OneContactStateType.Hold)
            {
                if ((info.Contacts[1].CurrentPosition - _originalPosition2).Length > MaxHoldDistance)
                {
                    _contactState2 = OneContactStateType.Drag;
                }
            }
            if (_contactState1 == OneContactStateType.Hold)
            {
                if ((info.Contacts[0].CurrentPosition - _originalPosition1).Length > MaxHoldDistance)
                {
                    _contactState1 = OneContactStateType.Drag;
                }
            }

            if (_contactState1 == OneContactStateType.Hold && _contactState2 == OneContactStateType.Hold)
            {
                // do nothing
            }            
            else 
            {
                // zoom and rotate
                Vector previousVector = _previousPosition2 - _previousPosition1;
                Vector currentVector = info.Contacts[1].CurrentPosition - info.Contacts[0].CurrentPosition;
                previousVector.Y *= -1;
                currentVector.Y *= -1;

                double previousDistance = previousVector.Length;
                double currentDistance = currentVector.Length;

                if (EnabledGestures.Contains(TwoContactsGestureType.Zoom))
                {
                    // determine the zoom factor
                    double delta = 1 + (currentDistance - previousDistance) / (previousDistance + ZoomParaA) * ZoomParaB;
                    double maxScale = MaxZoomFactor;
                    if (delta > 1 + maxScale)
                        delta = 1 + maxScale;
                    else if (delta < 1 - maxScale)
                        delta = 1 - maxScale;

                    if (double.IsNaN(delta))
                        delta = 1;
                    if (delta != 1 && GestureDetected != null)
                        GestureDetected(this, new GestureDetectedEventArgs(new ZoomGesture(delta, _originalCenter, info), 1));
                }

                if (EnabledGestures.Contains(TwoContactsGestureType.Rotate))
                {
                    previousVector.Normalize();
                    currentVector.Normalize();

                    double angle = Math.Asin(currentVector.X * previousVector.Y - previousVector.X * currentVector.Y) / Math.PI * 180;
                    double innerProduct = currentVector.X * previousVector.X + currentVector.Y * previousVector.Y;
                    if (innerProduct < 0)
                    {
                        if (angle > 0)
                            angle = 180 - angle;
                        else
                            angle = -180 - angle;
                    }

                    if (angle != 0 && GestureDetected != null)
                        GestureDetected(this, new GestureDetectedEventArgs(new RotateGesture(angle, _originalCenter, info), 1));
                }
            }

            _previousPosition1 = info.Contacts[0].CurrentPosition;
            _previousPosition2 = info.Contacts[1].CurrentPosition;
        }

        public void EndRecognize(GestureInfo info)
        {
            _contactState1 = _contactState2 = OneContactStateType.Hold;

            if (GestureDetected != null)
                GestureDetected(this, new GestureDetectedEventArgs(new TwoContactsEndGesture(info), 1));
        }

        public LocalGestureEngine GestureEngine
        {
            get;
            set;
        }

        public event GestureDetectedEventHandler GestureDetected;

        #endregion
    }

    public enum TwoContactsGestureType
    {
        Start,
        Zoom,
        Rotate,
        End,
    }

    public class TwoContactsGesture: Gesture
    {
        public TwoContactsGesture(GestureInfo info)
        {
            Info = info;
        }
    }

    public class TwoContactsStartGesture : TwoContactsGesture
    {
        public TwoContactsStartGesture(GestureInfo info): base(info)
        {

        }
    }

    public class TwoContactsEndGesture : TwoContactsGesture
    {
        public TwoContactsEndGesture(GestureInfo info)
            : base(info)
        {

        }
    }

    public class ZoomGesture: TwoContactsGesture
    {
        public ZoomGesture(double zoomFactor, Point center, GestureInfo info):base (info)
        {
            this.ZoomFactor = zoomFactor;
            this.ZoomCenter = center;
        }

        public double ZoomFactor { get; private set; }

        public Point ZoomCenter { get; private set; }

        public override string ToString()
        {
            return "Zoom :" + ZoomFactor;
        }
    }

    public class RotateGesture: TwoContactsGesture
    {
        public RotateGesture(double angle, Point center, GestureInfo info): base(info)
        {
            this.RotateAngle = angle;
            this.RotateCenter = center;
        }

        /// <summary>
        /// The angel rotated, from -180 to 180
        /// positive means the rotate is clockwise
        /// </summary>
        public double RotateAngle { get; set; }

        public Point RotateCenter { get; set; }

        public override string ToString()
        {
            return "Rotate :" + RotateAngle;
        }
    }    

}
